/*
 * Copyright (c) 2015, Freescale Semiconductor, Inc.
 * Copyright 2016-2017 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "fsl_qspi.h"

/* Component ID definition, used by tools. */
#ifndef FSL_COMPONENT_ID
#define FSL_COMPONENT_ID "platform.drivers.qspi"
#endif

/*******************************************************************************
 * Definitations
 ******************************************************************************/
enum _qspi_transfer_state
{
    kQSPI_TxBusy = 0x0U, /*!< QSPI is busy */
    kQSPI_TxIdle,        /*!< Transfer is done. */
    kQSPI_TxError        /*!< Transfer error occurred. */
};

#define QSPI_AHB_BUFFER_REG(base, index) (((volatile uint32_t *)&((base)->BUF0CR))[(index)])

#ifndef QuadSPI_SOCCR_DQS_LOOPBACK_EN_MASK
#define QuadSPI_SOCCR_DQS_LOOPBACK_EN_MASK (0x100U)
#endif

#ifndef QuadSPI_SOCCR_DQS_LOOPBACK_FROM_PAD_MASK
#define QuadSPI_SOCCR_DQS_LOOPBACK_FROM_PAD_MASK (0x200U)
#endif

#ifndef QuadSPI_SOCCR_DQS_PHASE_SEL_MASK
#define QuadSPI_SOCCR_DQS_PHASE_SEL_MASK (0xC00U)
#define QuadSPI_SOCCR_DQS_PHASE_SEL_SHIFT (10U)
#define QuadSPI_SOCCR_DQS_PHASE_SEL(x) \
    (((uint32_t)(((uint32_t)(x)) << QuadSPI_SOCCR_DQS_PHASE_SEL_SHIFT)) & QuadSPI_SOCCR_DQS_PHASE_SEL_MASK)
#endif

#ifndef QuadSPI_SOCCR_DQS_INV_EN_MASK
#define QuadSPI_SOCCR_DQS_INV_EN_MASK (0x1000U)
#define QuadSPI_SOCCR_DQS_INV_EN_SHIFT (12U)
#define QuadSPI_SOCCR_DQS_INV_EN(x) \
    (((uint32_t)(((uint32_t)(x)) << QuadSPI_SOCCR_DQS_INV_EN_SHIFT)) & QuadSPI_SOCCR_DQS_INV_EN_MASK)
#endif

#ifndef QuadSPI_SOCCR_DQS_IFA_DELAY_CHAIN_SEL_MASK
#define QuadSPI_SOCCR_DQS_IFA_DELAY_CHAIN_SEL_MASK (0x7F0000U)
#define QuadSPI_SOCCR_DQS_IFA_DELAY_CHAIN_SEL_SHIFT (16U)
#define QuadSPI_SOCCR_DQS_IFA_DELAY_CHAIN_SEL(x)                                    \
    (((uint32_t)(((uint32_t)(x)) << QuadSPI_SOCCR_DQS_IFA_DELAY_CHAIN_SEL_SHIFT)) & \
     QuadSPI_SOCCR_DQS_IFA_DELAY_CHAIN_SEL_MASK)
#endif

/*******************************************************************************
 * Prototypes
 ******************************************************************************/

/*******************************************************************************
 * Variables
 ******************************************************************************/
/* Base pointer array */
static QuadSPI_Type *const s_qspiBases[] = QuadSPI_BASE_PTRS;
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
/* Clock name array */
static const clock_ip_name_t s_qspiClock[] = QSPI_CLOCKS;
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

/*******************************************************************************
 * Code
 ******************************************************************************/
/*!
 * brief Get the instance number for QSPI.
 *
 * param base QSPI base pointer.
 */
uint32_t QSPI_GetInstance(QuadSPI_Type *base)
{
    uint32_t instance;

    /* Find the instance index from base address mappings. */
    for (instance = 0; instance < ARRAY_SIZE(s_qspiBases); instance++)
    {
        if (s_qspiBases[instance] == base)
        {
            break;
        }
    }

    assert(instance < ARRAY_SIZE(s_qspiBases));

    return instance;
}

/*!
 * brief Initializes the QSPI module and internal state.
 *
 * This function enables the clock for QSPI and also configures the QSPI with the
 * input configure parameters. Users should call this function before any QSPI operations.
 *
 * param base Pointer to QuadSPI Type.
 * param config QSPI configure structure.
 * param srcClock_Hz QSPI source clock frequency in Hz.
 */
void QSPI_Init(QuadSPI_Type *base, qspi_config_t *config, uint32_t srcClock_Hz)
{
    uint32_t i = 0;
    uint32_t val = 0;

#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    /* Enable QSPI clock */
    CLOCK_EnableClock(s_qspiClock[QSPI_GetInstance(base)]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

    /* Do software reset to QSPI module */
    QSPI_SoftwareReset(base);

    /* Clear the FIFO region */
    QSPI_ClearFifo(base, kQSPI_AllFifo);

    /* Configure QSPI */
    QSPI_Enable(base, false);

#if !defined(FSL_FEATURE_QSPI_CLOCK_CONTROL_EXTERNAL) || (!FSL_FEATURE_QSPI_CLOCK_CONTROL_EXTERNAL)
    /* Set qspi clock source */
    base->SOCCR = config->clockSource;

    /* Set the divider of QSPI clock */
    base->MCR &= ~QuadSPI_MCR_SCLKCFG_MASK;
    base->MCR |= QuadSPI_MCR_SCLKCFG(srcClock_Hz / config->baudRate - 1U);
#endif /* FSL_FEATURE_QSPI_CLOCK_CONTROL_EXTERNAL */

    /* Set AHB buffer size and buffer master */
    for (i = 0; i < FSL_FEATURE_QSPI_AHB_BUFFER_COUNT; i++)
    {
        val = QuadSPI_BUF0CR_MSTRID(config->AHBbufferMaster[i]) | QuadSPI_BUF0CR_ADATSZ(config->AHBbufferSize[i] / 8U);
        QSPI_AHB_BUFFER_REG(base, i) = val;
    }
    if (config->enableAHBbuffer3AllMaster)
    {
        base->BUF3CR |= QuadSPI_BUF3CR_ALLMST_MASK;
    }
    else
    {
        base->BUF3CR &= ~QuadSPI_BUF3CR_ALLMST_MASK;
    }

    /* Set watermark */
    base->RBCT &= ~QuadSPI_RBCT_WMRK_MASK;
    base->RBCT |= QuadSPI_RBCT_WMRK(config->rxWatermark - 1);

#if !defined(FSL_FEATURE_QSPI_HAS_NO_TXDMA) || (!FSL_FEATURE_QSPI_HAS_NO_TXDMA)
    base->TBCT &= ~QuadSPI_TBCT_WMRK_MASK;
    base->TBCT |= QuadSPI_TBCT_WMRK(config->txWatermark - 1);
#endif /* FSL_FEATURE_QSPI_HAS_NO_TXDMA */

    /* Enable QSPI module */
    if (config->enableQspi)
    {
        QSPI_Enable(base, true);
    }
}

/*!
 * brief Gets default settings for QSPI.
 *
 * param config QSPI configuration structure.
 */
void QSPI_GetDefaultQspiConfig(qspi_config_t *config)
{
    /* Initializes the configure structure to zero. */
    memset(config, 0, sizeof(*config));

    config->clockSource = 2U;
    config->baudRate = 24000000U;
    config->AHBbufferMaster[0] = 0xE;
    config->AHBbufferMaster[1] = 0xE;
    config->AHBbufferMaster[2] = 0xE;
    config->enableAHBbuffer3AllMaster = true;
    config->txWatermark = 8;
    config->rxWatermark = 8;
    config->enableQspi = true;
}

/*!
 * brief Deinitializes the QSPI module.
 *
 * Clears the QSPI state and  QSPI module registers.
 * param base Pointer to QuadSPI Type.
 */
void QSPI_Deinit(QuadSPI_Type *base)
{
    QSPI_Enable(base, false);
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    CLOCK_DisableClock(s_qspiClock[QSPI_GetInstance(base)]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */
}

/*!
 * brief Configures the serial flash parameter.
 *
 * This function configures the serial flash relevant parameters, such as the size, command, and so on.
 * The flash configuration value cannot have a default value. The user needs to configure it according to the
 * QSPI features.
 *
 * param base Pointer to QuadSPI Type.
 * param config Flash configuration parameters.
 */
void QSPI_SetFlashConfig(QuadSPI_Type *base, qspi_flash_config_t *config)
{
    uint32_t address = FSL_FEATURE_QSPI_AMBA_BASE + config->flashA1Size;
    uint32_t val = 0;
    uint32_t i = 0;

    /* Disable module */
    QSPI_Enable(base, false);

    /* Config the serial flash size */
    base->SFA1AD = address;
    address += config->flashA2Size;
    base->SFA2AD = address;
#if defined(FSL_FEATURE_QSPI_SUPPORT_PARALLEL_MODE) && (FSL_FEATURE_QSPI_SUPPORT_PARALLEL_MODE)
    address += config->flashB1Size;
    base->SFB1AD = address;
    address += config->flashB2Size;
    base->SFB2AD = address;
#endif /* FSL_FEATURE_QSPI_SUPPORT_PARALLEL_MODE */

#if !defined(FSL_FEATURE_QSPI_HAS_NO_SFACR) || (!FSL_FEATURE_QSPI_HAS_NO_SFACR)
    /* Set Word Addressable feature */
    val = QuadSPI_SFACR_WA(config->enableWordAddress) | QuadSPI_SFACR_CAS(config->cloumnspace);
    base->SFACR = val;
#endif /* FSL_FEATURE_QSPI_HAS_NO_SFACR */

    /* Config look up table */
    base->LUTKEY = 0x5AF05AF0U;
    base->LCKCR = 0x2U;
    for (i = 0; i < FSL_FEATURE_QSPI_LUT_DEPTH; i++)
    {
        base->LUT[i] = config->lookuptable[i];
    }
    base->LUTKEY = 0x5AF05AF0U;
    base->LCKCR = 0x1U;

#if !defined(FSL_FEATURE_QSPI_HAS_NO_TDH) || (!FSL_FEATURE_QSPI_HAS_NO_TDH)
    /* Config flash timing */
    val = QuadSPI_FLSHCR_TCSS(config->CSHoldTime) | QuadSPI_FLSHCR_TDH(config->dataHoldTime) |
          QuadSPI_FLSHCR_TCSH(config->CSSetupTime);
#else
    val = QuadSPI_FLSHCR_TCSS(config->CSHoldTime) | QuadSPI_FLSHCR_TCSH(config->CSSetupTime);
#endif /* FSL_FEATURE_QSPI_HAS_NO_TDH */
    base->FLSHCR = val;

    /* Set flash endianness */
    base->MCR &= ~QuadSPI_MCR_END_CFG_MASK;
    base->MCR |= QuadSPI_MCR_END_CFG(config->endian);

    /* Enable QSPI again */
    QSPI_Enable(base, true);
}

/*!
 * @brief Configures the serial flash DQS parameter.
 *
 * This function configures the serial flash DQS relevant parameters, such as the delay chain tap number, .
 * DQS shift phase, whether need to inverse and the rxc sample clock selection.
 *
 * @param base Pointer to QuadSPI Type.
 * @param config Dqs configuration parameters.
 */
void QSPI_SetDqsConfig(QuadSPI_Type *base, qspi_dqs_config_t *config)
{
    uint32_t soccrVal;
    uint32_t mcrVal;

    /* Disable module */
    QSPI_Enable(base, false);

    mcrVal = base->MCR;

    mcrVal &= ~(QuadSPI_MCR_DQS_EN_MASK | QuadSPI_MCR_DQS_LAT_EN_MASK);
    /* Enable DQS. */
    mcrVal |= QuadSPI_MCR_DQS_EN_MASK;

    /* Configure DQS phase, inverse and loopback atrribute */
    soccrVal = base->SOCCR;
    soccrVal &=
        ~(QuadSPI_SOCCR_DQS_LOOPBACK_EN_MASK | QuadSPI_SOCCR_DQS_LOOPBACK_FROM_PAD_MASK |
          QuadSPI_SOCCR_DQS_PHASE_SEL_MASK | QuadSPI_SOCCR_DQS_INV_EN_MASK | QuadSPI_SOCCR_DQS_IFA_DELAY_CHAIN_SEL_MASK
#if defined(QuadSPI_SOCCR_DQS_IFB_DELAY_CHAIN_SEL_MASK)
          | QuadSPI_SOCCR_DQS_IFB_DELAY_CHAIN_SEL_MASK
#endif
        );
    soccrVal |= QuadSPI_SOCCR_DQS_PHASE_SEL(config->shift);

    switch (config->rxSampleClock)
    {
        case kQSPI_ReadSampleClkInternalLoopback:
            soccrVal |= QuadSPI_SOCCR_DQS_LOOPBACK_EN_MASK;
            break;
        case kQSPI_ReadSampleClkExternalInputFromDqsPad:
            mcrVal |= QuadSPI_MCR_DQS_LAT_EN_MASK;
            break;
        case kQSPI_ReadSampleClkLoopbackFromDqsPad:
            soccrVal |= QuadSPI_SOCCR_DQS_LOOPBACK_FROM_PAD_MASK;
            break;
    }

    soccrVal |= (QuadSPI_SOCCR_DQS_INV_EN(config->enableDQSClkInverse) |
                 QuadSPI_SOCCR_DQS_IFA_DELAY_CHAIN_SEL(config->portADelayTapNum)
#if defined(QuadSPI_SOCCR_DQS_IFB_DELAY_CHAIN_SEL_MASK)
                 | QuadSPI_SOCCR_DQS_IFB_DELAY_CHAIN_SEL(config->portBDelayTapNum)
#endif
    );

    base->MCR = mcrVal;
    base->SOCCR = soccrVal;

    /* Enable QSPI again */
    QSPI_Enable(base, true);
}

/*!
 * brief Software reset for the QSPI logic.
 *
 * This function sets the software reset flags for both AHB and buffer domain and
 * resets both AHB buffer and also IP FIFOs.
 *
 * param base Pointer to QuadSPI Type.
 */
void QSPI_SoftwareReset(QuadSPI_Type *base)
{
    uint32_t i = 0;

    /* Reset AHB domain and buffer domian */
    base->MCR |= (QuadSPI_MCR_SWRSTHD_MASK | QuadSPI_MCR_SWRSTSD_MASK);

    /* Wait several time for the reset to finish, this method came from IC team */
    for (i = 0; i < 100; i++)
    {
        __NOP();
    }

    /* Disable QSPI module */
    QSPI_Enable(base, false);

    /* Clear the reset flags */
    base->MCR &= ~(QuadSPI_MCR_SWRSTHD_MASK | QuadSPI_MCR_SWRSTSD_MASK);

    /* Enable QSPI module */
    QSPI_Enable(base, true);
}

/*!
 * brief Gets the Rx data register address used for DMA operation.
 *
 * This function returns the Rx data register address or Rx buffer address
 * according to the Rx read area settings.
 *
 * param base Pointer to QuadSPI Type.
 * return QSPI Rx data register address.
 */
uint32_t QSPI_GetRxDataRegisterAddress(QuadSPI_Type *base)
{
    /* From RDBR */
    if (base->RBCT & QuadSPI_RBCT_RXBRD_MASK)
    {
        return (uint32_t)(&(base->RBDR[0]));
    }
    else
    {
        /* From ARDB */
        return FSL_FEATURE_QSPI_ARDB_BASE;
    }
}

/*! brief Executes IP commands located in LUT table.
 *
 * param base Pointer to QuadSPI Type.
 * param index IP command located in which LUT table index.
 */
void QSPI_ExecuteIPCommand(QuadSPI_Type *base, uint32_t index)
{
    while (QSPI_GetStatusFlags(base) & (kQSPI_Busy | kQSPI_IPAccess))
    {
    }
    QSPI_ClearCommandSequence(base, kQSPI_IPSeq);

    /* Write the seqid bit */
    base->IPCR = ((base->IPCR & (~QuadSPI_IPCR_SEQID_MASK)) | QuadSPI_IPCR_SEQID(index / 4U));
}

/*! brief Executes AHB commands located in LUT table.
 *
 * param base Pointer to QuadSPI Type.
 * param index AHB command located in which LUT table index.
 */
void QSPI_ExecuteAHBCommand(QuadSPI_Type *base, uint32_t index)
{
    while (QSPI_GetStatusFlags(base) & (kQSPI_Busy | kQSPI_AHBAccess))
    {
    }
    QSPI_ClearCommandSequence(base, kQSPI_BufferSeq);
    base->BFGENCR = ((base->BFGENCR & (~QuadSPI_BFGENCR_SEQID_MASK)) | QuadSPI_BFGENCR_SEQID(index / 4U));
}

/*! brief Updates the LUT table.
 *
 * param base Pointer to QuadSPI Type.
 * param index Which LUT index needs to be located. It should be an integer divided by 4.
 * param cmd Command sequence array.
 */
void QSPI_UpdateLUT(QuadSPI_Type *base, uint32_t index, uint32_t *cmd)
{
    uint8_t i = 0;

    /* Unlock the LUT */
    base->LUTKEY = 0x5AF05AF0U;
    base->LCKCR = 0x2U;

    /* Write data into LUT */
    for (i = 0; i < 4; i++)
    {
        base->LUT[index + i] = *cmd;
        cmd++;
    }

    /* Lcok LUT again */
    base->LUTKEY = 0x5AF05AF0U;
    base->LCKCR = 0x1U;
}

#if defined(FSL_FEATURE_QSPI_SOCCR_HAS_CLR_LPCAC) && (FSL_FEATURE_QSPI_SOCCR_HAS_CLR_LPCAC)

/*! brief Clears the QSPI cache.
 *
 * param base Pointer to QuadSPI Type.
 */
void QSPI_ClearCache(QuadSPI_Type *base)
{
    uint32_t soccrVal;

    soccrVal = base->SOCCR;
    /* Write 1 to clear cache. */
    base->SOCCR = (soccrVal | QuadSPI_SOCCR_CLR_LPCAC_MASK);

    /* Write 0 to after cache is cleared. */
    base->SOCCR = (soccrVal & (~QuadSPI_SOCCR_CLR_LPCAC_MASK));
}
#endif

/*! brief Set the RX buffer readout area.
 *
 * This function can set the RX buffer readout, from AHB bus or IP Bus.
 * param base QSPI base address.
 * param area QSPI Rx buffer readout area. AHB bus buffer or IP bus buffer.
 */
void QSPI_SetReadDataArea(QuadSPI_Type *base, qspi_read_area_t area)
{
    base->RBCT &= ~QuadSPI_RBCT_RXBRD_MASK;
    base->RBCT |= QuadSPI_RBCT_RXBRD(area);
}

/*!
 * brief Receives data from data FIFO.
 *
 * param base QSPI base pointer
 * return The data in the FIFO.
 */
uint32_t QSPI_ReadData(QuadSPI_Type *base)
{
    if (base->RBCT & QuadSPI_RBCT_RXBRD_MASK)
    {
        return base->RBDR[0];
    }
    else
    {
        /* Data from ARDB. */
        return *((uint32_t *)FSL_FEATURE_QSPI_ARDB_BASE);
    }
}

/*!
 * brief Sends a buffer of data bytes using a  blocking method.
 * note This function blocks via polling until all bytes have been sent.
 * param base QSPI base pointer
 * param buffer The data bytes to send
 * param size The number of data bytes to send
 */
void QSPI_WriteBlocking(QuadSPI_Type *base, uint32_t *buffer, size_t size)
{
    assert(size >= 16U);

    uint32_t i = 0;

    for (i = 0; i < size / 4U; i++)
    {
        /* Check if the buffer is full */
        while (QSPI_GetStatusFlags(base) & kQSPI_TxBufferFull)
        {
        }
        base->TBDR = *buffer++;
    }
}

/*!
 * brief Receives a buffer of data bytes using a blocking method.
 * note This function blocks via polling until all bytes have been sent. Users shall notice that
 * this receive size shall not bigger than 64 bytes. As this interface is used to read flash status registers.
 * For flash contents read, please use AHB bus read, this is much more efficiency.
 *
 * param base QSPI base pointer
 * param buffer The data bytes to send
 * param size The number of data bytes to receive
 */
void QSPI_ReadBlocking(QuadSPI_Type *base, uint32_t *buffer, size_t size)
{
    uint32_t i = 0;
    uint32_t j = 0;
    uint32_t temp = 0;
    uint32_t level = (base->RBCT & QuadSPI_RBCT_WMRK_MASK) + 1U;

    while (i < size / 4)
    {
        /* Check if there is data */
        if ((size / 4 - i) < level)
        {
            do
            {
                temp = (base->RBSR & QuadSPI_RBSR_RDBFL_MASK) >> QuadSPI_RBSR_RDBFL_SHIFT;
            } while (!temp);
        }
        else
        {
            while ((QSPI_GetStatusFlags(base) & kQSPI_RxWatermark) == 0U)
            {
            }
        }

        level = (level < (size / 4 - i)) ? level : (size / 4 - i);

        /* Data from RBDR */
        if (base->RBCT & QuadSPI_RBCT_RXBRD_MASK)
        {
            for (j = 0; j < level; j++)
            {
                buffer[i + j] = base->RBDR[j];
            }
        }
        else
        {
            /* Data from ARDB. */
            for (j = 0; j < level; j++)
            {
                buffer[i + j] = ((uint32_t *)FSL_FEATURE_QSPI_ARDB_BASE)[j];
            }
        }
        i += level;

        /* Clear the Buffer */
        QSPI_ClearErrorFlag(base, kQSPI_RxBufferDrain);
    }
}
